/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019.
 * Description: support early led
 * Author: qiuxi <qiuxi1@huawei.com>
 * Create: 2018-11-04
 *
 * Dec 2019 - zhangwen <zhangwen8@huawei.com>
 * refactor early led
 */

#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/delay.h>
#include <linux/of_fdt.h>
#include <asm-generic/early_ioremap.h>
#include "../gpio.h"

#include "led_common.h"

#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
#define  LED_GPIO_DIR_REG    HI_CPU_GPIO_DIR_REG
#define  LED_GPIO_OUTPUT_REG  HI_CPU_GPIO_OUTPUT_REG
#define  LED_IO_SIZE 0x1000
#elif defined(CONFIG_PPC32)
#include <../sysdev/fsl_soc.h>

#define  LED_GPIO_DIR_REG  0x00
#define  LED_GPIO_OUTPUT_REG  0x08
#define  LED_IO_SIZE 0x1000
#else
#error "arch is not supported"
#endif

const static char *rtos_led_match[] = {
	"rtos_led_fixpart",
	NULL
};

struct early_pin_info {
	volatile void *resource;
	unsigned int pin;
};

static struct early_pin_info early_led;

static ATOMIC_NOTIFIER_HEAD(earlyled_hook_notifier_chain);

static void  call_earlyled_hook_notifiers(void)
{
	atomic_notifier_call_chain(&earlyled_hook_notifier_chain, 0, NULL);
}

int __init bsp_early_led_shine(struct notifier_block *nb, unsigned long parm, void *buf)
{
	unsigned int value_read;

	if (IS_ERR((void *)early_led.resource))
		return 0;

#if defined(CONFIG_ARM)  || defined(CONFIG_ARM64)
	value_read = readl((const volatile void *)(early_led.resource + HI_CPU_GPIO_INT_EN_REG));
	value_read &= ~early_led.pin;
	writel(value_read, (volatile void *)(early_led.resource + HI_CPU_GPIO_INT_EN_REG));
#endif

	/* set as output pin */
	value_read = readl((volatile void *)(early_led.resource + LED_GPIO_DIR_REG));
	value_read |= early_led.pin;
	writel(value_read, (volatile void *)(early_led.resource + LED_GPIO_DIR_REG));

	value_read = readl(early_led.resource + LED_GPIO_OUTPUT_REG);
	value_read ^= early_led.pin;
	writel(value_read, early_led.resource + LED_GPIO_OUTPUT_REG);

	return 0;
}

static struct notifier_block early_led_notifier __initdata = {
	.notifier_call = bsp_early_led_shine,
};

static int __init rtos_dt_find_led(unsigned long node,
		const char *uname, int depth, void *data)
{
	unsigned long base;
	unsigned long ret;

	if (of_flat_dt_match(node, rtos_led_match)) {
		__be32 *addr = NULL;
		__be32 *pin = NULL;

		addr = (__be32 *)of_get_flat_dt_prop(node, "port", NULL);
		if (!addr) {
			printk(KERN_ERR"Cannot find reg in led, something must be wrong in dts\n");
			return -ENODEV;
		}

		base = be32_to_cpup(addr);

#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
		early_led.resource = (void *)early_ioremap(base, LED_IO_SIZE);
		if (early_led.resource == NULL) {
			printk("Ioremap fail for led\n");
			return -ENOMEM;
		}
#elif defined(CONFIG_PPC32)
		early_led.resource = (void *)ioremap(get_immrbase() + base, LED_IO_SIZE);
		if (early_led.resource == NULL) {
			printk("Ioremap fail for led\n");
			return -ENOMEM;
		}
#endif
		pin = (__be32 *)of_get_flat_dt_prop(node, "pin", NULL);
		if (!pin) {
			printk(KERN_ERR"Cannot find pin in led, something must be wrong in dts\n");
			return -EINVAL;
		}
		early_led.pin = be32_to_cpup(pin);
		ret = atomic_notifier_chain_register(&earlyled_hook_notifier_chain, &early_led_notifier);
		if (ret)
			return ret;

		call_earlyled_hook_notifiers();
	}

	return 0;
}

void __init rtos_dt_map_led(void)
{
	static int is_inited;

	if (0 == is_inited) {
		is_inited = 1;
	} else {
		return;
	}

	if (of_scan_flat_dt(rtos_dt_find_led, NULL)) {
		printk(KERN_ERR"Map early led error.\n");
	}
}

void __init led_early_breathing(void)
{
	struct device_node *np = NULL;

	/*
	 * early led use node as below:
	 * old arch
	 * 1.rtos_led_cpld_fixpart(not support)
	 * 2.rtos_led_fixpart
	 *
	 * new arch
	 * 1.rtos_led_ctrl
	 *
	 */
	if (of_find_compatible_node(NULL, NULL, "rtos_led_cpld_fixpart"))
		return;

	np = of_find_compatible_node(NULL, NULL, "rtos_led_fixpart");
	if (np) {
		rtos_dt_map_led();
	} else {
		led_base_init(DROL_EARLY_IOREMAP, DROL_EARLY_IOUNMAP);
		led_unmap(DROL_EARLY_IOUNMAP);
	}
}
